/*
 * Copyright 2012-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.context.event;

import java.time.Duration;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.springframework.boot.ConfigurableBootstrapContext;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.boot.availability.AvailabilityChangeEvent;
import org.springframework.boot.availability.LivenessState;
import org.springframework.boot.availability.ReadinessState;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ApplicationEventMulticaster;
import org.springframework.context.event.SimpleApplicationEventMulticaster;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.util.ErrorHandler;

/**
 * {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
 * <p>
 * Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
 * before the context is actually refreshed.
 *
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Andy Wilkinson
 * @author Artsiom Yudovin
 * @author Brian Clozel
 * @author Chris Bono
 * @since 1.0.0
 */
// EventPublishingRunListener是Springboot为我们提供的一套事件发布机制，spring的事件发布机制基于spring容器中实现ApplicationListener接口的bean的，
// 而Springboot的事件发布机制基于spi机制加载的ApplicationListener接口全类名对应的一系列使用逗号隔开的字符串通过反射实例化的对象
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

	private final SpringApplication application;

	private final String[] args;
	// Springboot的事件发布机制都是依靠这个在构造方法中实例化的多播器完成的
	private final SimpleApplicationEventMulticaster initialMulticaster;
	// 实现了SpringApplicationRunListener接口,并且在META-INF/spring.factories中配置了,
	// 那么它就一定要有这两个参数的构造器, 这是在springboot源码中写死的 或者说规定好的, 我们自己模拟写的话,也要这样
	public EventPublishingRunListener(SpringApplication application, String[] args) {
		this.application = application;
		this.args = args;
		// 这里初始化了一个多播器, 注意, 相对之前看的spring的事件发布机制源码(该构造方法下面带有beanFactory参数的即为spring创建多播器时的构造方法),
		// 这里并没有传beanFactory进去 因为spi机制加载SpringApplicationRunListener这个类的时候, 还没开始创建ApplicationContext
		this.initialMulticaster = new SimpleApplicationEventMulticaster();
		// 下面这个getListeners(), 依旧是从spi机制加载的ApplicationListener,这个是在new SpringApplication()过程中通过spi加载ApplicationListener,
		// 并且设置给了SpringApplication中的listeners属性
		// 在这里跟spring的事件发布机制差不多, 把ApplicationListener接口实现类对象放到多播器里 这样一旦使用多播器发布事件, 就能通知这些监听器要回调相应的方法了
		// 与spring的事件发布机制区别在于, 这些ApplicationListener接口的实现类对象是通过spi机制加载的, 因此需要配置在META-INF/spring.factories文件中,
		// 这样才能在new SpringApplication()过程中,读取并实例化, 交给SpringApplication对象
		for (ApplicationListener<?> listener : application.getListeners()) {
			this.initialMulticaster.addApplicationListener(listener);
		}
	}
	//这里指定当前EventPublingRunListener的在spi中的实例化顺序
	@Override
	public int getOrder() {
		return 0;
	}
    // springBoot启动过程中发生事件时事件广播者是层层委托职责的，起初由SpringApplicationRunListeners对象承担，然后SpringApplicationRunListeners对象
	// 将广播事件职责委托给EventPublishingRunListener对象，最终EventPublishingRunListener对象将广播事件的职责委托给SimpleApplicationEventMulticaster对象
	//注意：发布的时候会传递不同的事件类型到广播器中，比如开始事件类型为ApplicationStartingEvent，环境准备完成事件为ApplicationEnvironmentPreparedEvent
	// 而我们的监听程序都会实现ApplicationListener接口中的onApplicationEvent方法，其会根据onApplicationEvent中的参数类型来接受事件，
	// 比如：onApplicationEvent(ApplicationEnvironmentPreparedEvent event) 意味着只有当类型为ApplicationEnvironmentPreparedEvent环境准备事件发布到广播器中才会被触发
	@Override
	public void starting(ConfigurableBootstrapContext bootstrapContext) {
		this.initialMulticaster
				.multicastEvent(new ApplicationStartingEvent(bootstrapContext, this.application, this.args));
	}

	// multicastEvent方法调用链：multicastEvent->getApplicationListeners(event, type)->retrieveApplicationListeners(eventType, sourceType, newRetriever)
	//->supportsEvent(listener, eventType, sourceType) 从spring.factory中初始的Listener集合中筛选出符合条件的applicationListener对象
	// ->invokeListener(listener, event) 最终调用onApplicationEvent方法
	@Override
	public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
			ConfigurableEnvironment environment) {
		//发布ApplicationEnvironmentPreparedEvent事件，监听该事件的相关监听器会触发
		this.initialMulticaster.multicastEvent(
				new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
	}

	@Override
	public void contextPrepared(ConfigurableApplicationContext context) {
		this.initialMulticaster
				.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
	}

	@Override
	public void contextLoaded(ConfigurableApplicationContext context) {
		for (ApplicationListener<?> listener : this.application.getListeners()) {
			if (listener instanceof ApplicationContextAware) {
				((ApplicationContextAware) listener).setApplicationContext(context);
			}
			context.addApplicationListener(listener);
		}
		this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
	}

	@Override
	public void started(ConfigurableApplicationContext context, Duration timeTaken) {
		context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context, timeTaken));
		AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
	}

	@Override
	public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
		context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context, timeTaken));
		AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
	}

	@Override
	public void failed(ConfigurableApplicationContext context, Throwable exception) {
		ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
		if (context != null && context.isActive()) {
			// Listeners have been registered to the application context so we should
			// use it at this point if we can
			context.publishEvent(event);
		}
		else {
			// An inactive context may not have a multicaster so we use our multicaster to
			// call all of the context's listeners instead
			if (context instanceof AbstractApplicationContext) {
				for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
						.getApplicationListeners()) {
					this.initialMulticaster.addApplicationListener(listener);
				}
			}
			this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
			this.initialMulticaster.multicastEvent(event);
		}
	}

	private static class LoggingErrorHandler implements ErrorHandler {

		private static final Log logger = LogFactory.getLog(EventPublishingRunListener.class);

		@Override
		public void handleError(Throwable throwable) {
			logger.warn("Error calling ApplicationEventListener", throwable);
		}

	}

}
